今天來介紹 trpc 中非常重要的功能 input/output validate ,有了 input 跟 output 我們就可以透過 type infer 去定義你的 api input 內容與 output 結果。
input : 用來驗證 client 端傳送內容。output : 檢查 procedure return 結果是否正確,這邊有一個小提示是, output 並不是必要選項,除非你是以下的情境則可以考慮使用 :
client 端的格式是否正確。code 可閱讀性,如果 procedure function 邏輯越來越複雜,就可以不用看 code 理解 output 內容是什麼了。以下 demo 是用來驗證 input 的 type 是否是 string,如果不是就 throw error,這個一個小知識點,如果在 input 中有發生任何錯誤,將會返回 error 結果到 client 端。
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.query((opts) => {
const { input } = opts;
const input: string
return `hello ${input}`;
}),
});
export type AppRouter = typeof appRouter;
然後加上 output ,如果返回結果不是 string 就拋出錯誤。
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.output((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Output is not a string');
})
.query((opts) => {
const { input } = opts;
const input: string
return `hello ${input}`;
}),
});
但你以為只有這樣嗎?其實非也 input 跟 output 還可以結合現在很火的 zod 去幫你 validate 內容喔~我們以昨天的 gretting api 為例,這邊我們新增 input 裡面的屬性是 name,最後 return name 結果。
export const appRouter = router({
greeting: publicProcedure
.input(z.object({
name: z.string()
}))
.query(({ input }) => `hello ${input.name} `),
});
很有趣的是 trpc 這邊會自動幫你 type infer input type 到你的 query function 中,不用而外再寫 type 了。

之後我們在 client 添加 name
// src/index.ts
export default function Home() {
const { data, isLoading, isError } = api.greeting.useQuery({ name: 'Danny' })
if (isLoading) return 'isLoading'
if (isError) return 'isError'
return (
<>
{data}
</>
);
}
之後我們檢查 network response

這邊有一個很特別的地方是 input 其實就是 query 的用法哈哈,還有就是 trpc 會幫你把 query hash 起來,這樣對於 query 的安全性蠻不錯的,不用擔心 input 內容被發現。

最後我們看畫面,這樣我們就成功 call 到 greeting 了~

備註: 如果讀者不習慣用 zod 的話也可以用其他 input schema 的工具例如 Yup 等等,如果讀者有需要可以自行到官網查看 範例。
Procedures 除了可以透過 initTRPC.create() 外, Procedures 還可以繼承其他的 Procedures 喔~ 下面就是繼承 publicProcedure 然後去做 authorized 。
export const authorizedProcedure = publicProcedure
.input(z.object({ townName: z.string() }))
// trpc 的 middleware 寫法,寫法跟 express 根本一模ㄧ樣
.use((opts) => {
if (opts.input.townName !== 'Pucklechurch') {
throw new TRPCError({
code: 'FORBIDDEN',
message: "We don't take kindly to out-of-town folk",
});
}
return opts.next();
});
export const appRouter = t.router({
hello: publicProcedure.query(() => {
return {
message: 'hello world',
};
}),
getPosts: authorizedProcedure.query(async (opts) => {
const POSTS = await DBCALL()
return {
POSTS
};
}),
});
這樣我們的 appRoute 就可以跟去不同 procedure 管理 api 是否需要驗證 authorized。
trpc 提供 merge routes 的 function,可以先宣告 const mergeRouters = t.mergeRouters 之後到 route/index 透過 mergeRouters,整合所有 appRouter。
// utils/trpc
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
export const publicProcedure = t.procedure;
export const mergeRouters = t.mergeRouters;
// routers/index
import { router, publicProcedure, mergeRouters } from '../trpc';
import { z } from 'zod';
import { userRouter } from './user';
import { postRouter } from './post';
const appRouter = mergeRouters(userRouter, postRouter)
export type AppRouter = typeof appRouter;
// routers/user
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';
export const userRouter = router({
userList: publicProcedure.query(() => {
// [..]
return [];
}),
});
// routers/post
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';
export const postRouter = router({
postCreate: publicProcedure
.input(
z.object({
title: z.string(),
}),
)
.mutation((opts) => {
const { input } = opts;
const input: {
title: string;
}
// [...]
}),
postList: publicProcedure.query(() => {
// ...
return [];
}),
});
今天內容到這邊明天繼續~
相關連結github : https://github.com/Danny101201/next_demo/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y